use serde::{Deserialize, Serialize};
use std::str::FromStr;

use crate::CustomError;

#[derive(Debug, Serialize, Deserialize)]
pub struct Table {
    pub table_name: String,
    pub columns: Vec<Column>,
    pub primary_key: usize,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Column {
    pub name: String,
    pub data_type: DataType,
    pub default: Option<Value>,
    pub nullable: bool,
    pub primary_key: bool,
    pub comment: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum DataType {
    Int,
    Float,
    Text,
    Bool,
    TimeStamp,
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Insert {
    pub table_name: String,
    pub literals: Option<Vec<String>>,
    pub val: Vec<Vec<String>>,
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Select {
    pub table_name: String,
    pub literal: SelectLiteral,
    pub swhere: Option<Vec<Entry>>,
    pub order: Option<Order>,
    pub group_by: Option<Vec<String>>,
    pub limit: Option<usize>,
    pub offset: Option<usize>,
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Order {
    pub order_by: String,
    pub order_sort: OrderSort,
}

impl Order {
    pub fn new(order_by: String, order_sort: String) -> Self {
        Order {
            order_by,
            order_sort: order_sort.into(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum SelectLiteral {
    All,
    Literal(Vec<String>),
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Entry {
    pub key: String,
    pub val: String,
}

impl Entry {
    pub fn new(key: String, val: String) -> Self {
        Entry { key, val }
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum OrderSort {
    Asc,
    Desc,
}

impl From<String> for OrderSort {
    fn from(sort: String) -> Self {
        match sort.as_str() {
            "asc" => OrderSort::Asc,
            "desc" => OrderSort::Desc,
            _ => OrderSort::Asc,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Value {
    Int(i64),
    Float(f64),
    Text(String),
    Bool(bool),
    TimeStamp(i64),
    Null,
}

#[derive(Debug)]
pub enum Symbol {
    LeftBracket,  //(
    RightBracket, //)
    Asterisk,     //*
    SingleQuote,  //'
}

impl FromStr for DataType {
    type Err = CustomError;

    fn from_str(data_type: &str) -> Result<Self, Self::Err> {
        let dtype = match data_type {
            "int" => DataType::Int,
            "float" => DataType::Float,
            "text" => DataType::Text,
            "bool" => DataType::Bool,
            "timestamp" => DataType::TimeStamp,
            _ => return Err(CustomError::SqlParseError.into()),
        };
        Ok(dtype)
    }
}

#[derive(Debug)]
pub enum Keyword {
    Insert,
    Delete,
    Update,
    Select,
    Into,
    Values,
    Primary,
    Key,
    Comment,
    Null,
    Not,
    Default,
    Table,
    From,
    By,
    Order,
    Group,
    Limit,
    Offset,
    And,
    Set,
    Where,
}

impl Into<&str> for Keyword {
    fn into(self) -> &'static str {
        match self {
            Keyword::Comment => "comment",
            Keyword::Default => "default",
            Keyword::Delete => "delete",
            Keyword::Insert => "insert",
            Keyword::Into => "into",
            Keyword::Not => "not",
            Keyword::Null => "null",
            Keyword::Primary => "primary",
            Keyword::Select => "select",
            Keyword::Update => "update",
            Keyword::Values => "values",
            Keyword::Key => "key",
            Keyword::Table => "table",
            Keyword::From => "from",
            Keyword::By => "by",
            Keyword::Group => "group",
            Keyword::Limit => "limit",
            Keyword::Offset => "offset",
            Keyword::Order => "order",
            Keyword::And => "and",
            Keyword::Set => "set",
            Keyword::Where => "where",
        }
    }
}

#[derive(Debug)]
pub struct Update {
    pub table_name: String,
    pub columns: Vec<Entry>,
    pub uwhere: Option<Vec<Entry>>,
}